
# calendar
from datetime import date
from itertools import groupby

from django.utils.html import conditional_escape as esc
from django.utils.encoding import smart_str, smart_unicode

from tj3.models import *
from settings import *
import os, sys, shutil, re
import os.path
import subprocess
import csv
from django.core import serializers
import pygraphviz as pgv
from lxml import etree
from string import capwords
from datetime import datetime

# to handle emails
import poplib
import getpass
import email
import smtplib
from email import Encoders
from email.MIMEBase import MIMEBase
from email.MIMEMultipart import MIMEMultipart
from email.Utils import formatdate
from email.mime.text import MIMEText

TASK_URL='http://feng-vm.crunchtime.local/yum/tj3/task/detail/'

def TaskjugglerCompile (proj):
	# step 3: call tj3 to compile
	dest=os.path.join(proj.working_path, 'reports')
	source=os.path.join(proj.working_path,'plan.tjp')
	if subprocess.call(['/usr/local/bin/tj3','-o',dest,source]) != 0:
		1/0
	subprocess.call(['/usr/bin/git', 'commit -a -m "compiled"'])
	return dest
	
def ProjectTaskEmail (proj):
	tasks=Task.tree.filter(project=proj)
	bookings=Booking.objects.filter(task__in=tasks).order_by('-when')
	journals=dict([(b,Journal.objects.filter(booking=b)) for b in bookings])
	
	# step 1: dump task.tji to working_path
	TaskDumpToFile(tasks, proj.working_path)
	
	# step 2: dump journals
	JournalDumpToFile (proj, journals, dest=proj.working_path)
	
	# step 3: call tj3
	dest=TaskjugglerCompile(proj)
	
	# step 4: parse timesheet.csv and send notification emails 		
	# CSV is in format: ID, work load on a day
	workload={}
	header=[]
	rownum = 0
	current_resource = None
	for r in csv.reader(open(os.path.join(dest,'timesheet.csv'),'r'),delimiter=';'):
		# remember the headerline, it has the dates
		if rownum == 0: 
			header=r
			rownum += 1
			continue

		# look up task
		tasks = Task.objects.filter(full_path=r[0])
		if len(tasks): 
			task=tasks[0]
			if not task.is_leaf_node(): continue #only leaf task is meaningful
			
		else: # try if this is a resource, this to update current_resource
			first, last, worklimit = tuple(r[0].split('_'))
			resources = Resource.objects.filter(first_name=first).filter(last_name=last).filter(work_limit=int(worklimit))
			if len(resources): 
				current_resource=resources[0]
				# add to the workload dict
				if current_resource not in workload:
					workload[current_resource]={}
			continue # if not a task line, we do not need to look further!
				
		# search for workload
		for i in range(1,len(header)):
			assignment_day = header[i]
			load=r[i]
			if re.search('[\d.]*[hdwHDW]', load):
				if assignment_day not in workload[current_resource]: workload[current_resource][assignment_day]=[]
				workload[current_resource][assignment_day].append((task.full_path,load))

	for resource,loads in workload.iteritems():
		# the actual email message
		msg = 'Hi %s,<p>A reminder that the following tasks have been scheduled for you:' % capwords(resource.first_name)

		assignment_days = loads.keys()
		assignment_days.sort()
		for d in assignment_days:
			msg += '<h4>%s</h4>'% d
			msg += '<ul>'
			for (task_name, load) in loads[d]:
				# find task by searching task_name as full_path
				task = Task.objects.filter(full_path=task_name)[0]
				msg += '<li><font color=red>%s</font>, <a href="%s">%s</a>' % (load,TASK_URL+str(task.id)+'/', task_name.replace('_', ' ').replace('.','/'))			
			msg += '</ul>'
		msg += '<p>You can update the task status by following the task URLs.'
		msg += '''<p>Best regards,<p>%s Project''' % proj.name
		subject = '%s Project Work Schedule' % proj.name

		if len(loads): # only send resource email if s/he has a work load
			# SendEmail, haha
			to = resource.email
			SendTimesheet(TO=to, subject=subject, message=msg)
		
			# always send a copy to me
			to = 'fxia@crunchtime.com'
			SendTimesheet(TO=to, subject=subject, message=msg)
			

def SendTimesheet(TO, FROM='noreply@crunchtime.com', subject=None, message=''):
	msg = MIMEMultipart('alternative')
	msg['From']=FROM
	msg['To']=TO
	msg['Date']=formatdate(localtime=True)

	# Record the MIME types of both parts - text/plain and text/html.
	#part1 = MIMEText(message, 'plain')
	part2 = MIMEText(message, 'html')

	# Attach parts into message container.
	# According to RFC 2046, the last part of a multipart message, in this case
	# the HTML message, is best and preferred.
	#msg.attach(part1)
	msg.attach(part2)


	if subject: msg['Subject']=subject
	else: msg['Subject']='Greeting'
	
	# You need to set up localhost EXIM to send the email
	HOST='localhost'
	server=smtplib.SMTP(HOST)
    #try:
	failed = server.sendmail(msg['From'], msg['To'], msg.as_string())
	server.close()
    #except Exception, e:
    #    errorMsg = "Unable to send email. Error: %s" % str(e)

def ReadTimesheet ():
	MAILSERVER='mail.crunchtime.local'
	mServer=poplib.POP3(MAILSERVER)
	mServer.user('fxia')
	mServer.pass_('XFxf12345')
	numMsg = len(mServer.list()[1])
	
	# get the lastest 100 emails
	messages = [mServer.retr(i) for i in range(numMsg,numMsg-100,-1)]
	messages = ['\n'.join(msg[1]) for msg in messages]
	messages= [parser.Parser().parsestr(msg) for msg in messages]
	for msg in messages:
		print msg['subject']
	mServer.quit()
                                
def TaskGraph (proj):
	tasks = Task.objects.filter(project=proj)
	
	# dump a DOT graph, haha
	G=pgv.AGraph(strict=True,directed=True)
	G.graph_attr['label']=str(proj.name)
	G.graph_attr['rankdir']='LR'
	G.graph_attr['overlap']='scale'
	
	for t in tasks:
		if t.is_root_node(): 
			G.add_node(t.id, label=str(t.name), shape='box',fillcolor='skyblue',style='filled')
		else: 
			G.add_node(t.id, label=str(t.name), shape='plaintext')
	
	existing_links=[]
	for t in tasks:
		for d in TaskDependency.objects.filter(from_task=t):
			G.add_edge(d.to_task.id,t.id,style='dotted',color='red')
			existing_links.append((d.to_task.id,t.id))
	
	for t in tasks:		
		# hiearchy
		for c in t.get_children():
			if (t.id,c.id) not in existing_links:
				G.add_edge(t.id,c.id, concentrate='true', arrowhead='none')
			
	G.write(os.path.join(STATIC_DOC_ROOT,'tmp',proj.name+'.dot'))
	diagram = os.path.join(STATIC_DOC_ROOT,'tmp',proj.name+'.png')
	G.draw(diagram, format='png',prog='neato')
	return open(diagram).read()
		
def TaskDumpToFile(tasks, dest='/tmp'):
	# populate full-path of a task
	for t in tasks:
		# populate full path
		paths =[CleanName(a.name) for a in t.get_ancestors()]
		paths.append(CleanName(t.name))
		t.full_path = '.'.join(paths)
		t.save()

	tji_file = os.path.join(dest, 'task.tji')
	f=open(tji_file,'w')
	for t in tasks:
		if t.is_root_node():
			TaskDump(f,t,0)
	f.close()
	return tji_file

def TaskDumpToFreemind(proj, tasks):
	# write the root nodes
	root=etree.Element('map')
	root.set('version','0.9.0')
	project = etree.SubElement(root,'node')
	project.set('TEXT',proj.name)
	project.set('ID', str(proj.id))
	
	# dump tasks as nodes
	for t in tasks:
		if t.is_root_node():
			TaskIterateFreemind(project,t)
			
	# write the XML to Freemind file
	return etree.tostring(root, pretty_print=True)
		
def TaskIterateFreemind(root, task):
	# walk the task tree
	# this is essentially flattening the task tree to a XML
	
	# create the task node
	child = etree.SubElement(root,'node')
	child.set('CREATED',str(task.created))
	child.set('MODIFIED',str(task.last_modified))
	child.set('ID',str(task.id))
	child.set('TEXT',task.name)
	wbs_attr=etree.SubElement(child,'attribute')
	wbs_attr.set('NAME','REGISTERED')
	wbs_attr.set('VALUE',str(task.id))
	
	# create dependency as arrowlinks
	deps = TaskDependency.objects.filter(to_task=task)
	for d in deps:
		dep = etree.SubElement(child, 'arrowlink')
		dep.set('DESTINATION', str(d.from_task.id))

	# walk for its immediate children
	for d in task.get_children():
		TaskIterateFreemind(child,d)
	
def TaskDump(f, task, level):
	periods=TaskPeriod.objects.filter(task=task)
	resources=AllocateResource.objects.filter(task=task)
	deps = TaskDependency.objects.filter(from_task=task)
	overhead=level*'\t'
	
	f.write(overhead+'task %s "%s" {\n' % (CleanName(task.name), task.description))
	overhead+='\t'
	
	# task and resource tags (flags)
	flags = [t.name for t in task.tags.all()]
	for r in resources:
		for a in r.primary.tags.all(): flags.append(a.name)
		if r.alternative:
			for a in r.alternative.tags.all(): flags.append(a.name)
	flags=list(set(flags))				
	if len(flags):
		f.write(overhead+'flags '+','.join(flags)+'\n')
	
	# task periods
	for p in periods:
		if p.milestone: f.write(overhead+'milestone\n')
		if p.start: f.write(overhead+'%s: start %s\n' % (p.scenario, p.start))
		if p.max_start: f.write(overhead+'%s: maxstart %s\n' % (p.scenario, p.max_start))
		if p.min_start: f.write(overhead+'%s: minstart %s\n' % (p.scenario, p.min_start))
		if p.end: f.write(overhead+'%s: end %s\n' % (p.scenario, p.end))
		if p.max_end: f.write(overhead+'%s: maxend %s\n' % (p.scenario, p.max_end))
		if p.min_end: f.write(overhead+'%s: minend %s\n' % (p.scenario, p.min_end))
		if p.length: f.write(overhead+'%s: length %s\n' % (p.scenario, p.length))
		if p.duration: f.write(overhead+'%s: duration %s\n' % (p.scenario, p.duration))
		if p.effort: f.write(overhead+'%s: effort %s\n' % (p.scenario, p.effort))
		if p.complete != None:
			if int(p.complete)>=100:
				f.write(overhead+'%s: scheduled\n' % p.scenario)
			f.write(overhead+'%s: complete %s\n' %(p.scenario, p.complete))
	
	# write all resources
	tt=[]
	for r in resources:
		t = 'allocate %s' % r.primary
		if r.alternative: t += '{alternative %s}' % r.alternative
		tt.append(overhead+t)
	f.write('\n'.join(tt)+'\n')
	
	# next, write resource limits
	f.write(overhead+'limits {'+'\n')
	for r in resources:
		if r.primary.work_limit:
			if r.primary.work_limit.dailymax:
				f.write(overhead+'dailymax %s {resources %s}\n' % 
				(r.primary.work_limit.dailymax, r.primary))
			if r.primary.work_limit.dailymin:
				f.write(overhead+'dailymin %s {resources %s}\n' % 
				(r.primary.work_limit.dailymin, r.primary))
			if r.primary.work_limit.weeklymax:
				f.write(overhead+'weeklymax %s {resources %s}\n' % 
				(r.primary.work_limit.weeklymax, r.primary))
			if r.primary.work_limit.weeklymin:
				f.write(overhead+'weeklymin %s {resources %s}\n' % 
				(r.primary.work_limit.weeklymin, r.primary))
			if r.primary.work_limit.monthlymax:
				f.write(overhead+'monthlymax %s {resources %s}\n' % 
				(r.primary.work_limit.monthlymax, r.primary))
			if r.primary.work_limit.monthlymin:
				f.write(overhead+'monthlymin %s {resources %s}\n' % 
				(r.primary.work_limit.monthlymin, r.primary))
			if r.primary.work_limit.maximum:
				f.write(overhead+'maximum %s {resources %s}\n' % 
				(r.primary.work_limit.maximum, r.primary))
			if r.primary.work_limit.minimum:
				f.write(overhead+'minimum %s {resources %s}\n' % 
				(r.primary.work_limit.minimum, r.primary))
	
		if r.alternative:
			if r.alternative.work_limit:
				if r.alternative.work_limit.dailymax:
					f.write(overhead+'dailymax %s {resources %s}\n' % 
					(r.alternative.work_limit.dailymax, r.alternative))
				if r.alternative.work_limit.dailymin:
					f.write(overhead+'dailymin %s {resources %s}\n' % 
					(r.alternative.work_limit.dailymin, r.alternative))
				if r.alternative.work_limit.weeklymax:
					f.write(overhead+'weeklymax %s {resources %s}\n' % 
					(r.alternative.work_limit.weeklymax, r.alternative))
				if r.alternative.work_limit.weeklymin:
					f.write(overhead+'weeklymin %s {resources %s}\n' % 
					(r.alternative.work_limit.weeklymin, r.alternative))
				if r.alternative.work_limit.monthlymax:
					f.write(overhead+'monthlymax %s {resources %s}\n' % 
					(r.alternative.work_limit.monthlymax, r.alternative))
				if r.alternative.work_limit.monthlymin:
					f.write(overhead+'monthlymin %s {resources %s}\n' % 
					(r.alternative.work_limit.monthlymin, r.alternative))
				if r.alternative.work_limit.maximum:
					f.write(overhead+'maximum %s {resources %s}\n' % 
					(r.alternative.work_limit.maximum, r.alternative))
				if r.alternative.work_limit.minimum:
					f.write(overhead+'minimum %s {resouces %s}\n' % 
					(r.alternative.work_limit.minimum, r.alternative))
	f.write(overhead+'}\n')
				
	# write task dependencies
	for d in deps:
		f.write(overhead+'%s %s' %(d.type, d.to_task.full_path))
		if d.on_what:
			f.write('{ %s ' % d.on_what)
			if d.gap_duration:
				f.write('gapduration %s ' % d.gap_duration)
			if d.gap_length:
				f.write('gaplength %s ' % d.gap_length)
			f.write('}\n')
	
	if not task.is_leaf_node():
		for d in task.get_children():
			TaskDump(f,d,level+1)

	overhead=level*'\t'
	f.write(overhead+'}\n')
	
def JournalDumpToFile(proj, journals, dest='/tmp'):
	# dump to journal file
	f = open(os.path.join(dest,'journals.tji'),'w')
	for b, j_list in journals.iteritems():
		f.write('supplement task %s {\n' % b.task.full_path)
		f.write('\tactual: booking %s %s-%s + %s' % (b.resource,b.when,b.part_of_day,b.length))
		if b.overtime: f.write('{ overtime %s }' % b.overtime)
		f.write('\n')
			
		# write journals
		for j in j_list:
			f.write('\tjournalentry %s "%s" {\n' % (b.when, smart_str(j.summary)))
			f.write('\t\talert %s\n' % j.alert)
			f.write('\t\tsummary "%s"\n' % smart_str(j.summary))
			f.write('\t\tdetails -8<-\n')
			f.write('\t\t\tDescription:\n\t\t\t%s\n' % smart_str(j.description))
			f.write('\t\t\tMemo:\n\t\t\t%s\n' % j.memo)
			f.write('\t\t->8-\n\t}\n')
		f.write('}\n')
	f.close()

def CleanName(s):
	# clean up task name, replace ':' and white spaces with underscore '_'
	# first, replace all these with white spaces
	s=s.replace('!','')
	s=re.sub('[_:.(&)-/]',' ',s)
	
	#s = s.replace('_',' ').replace(':',' ').replace('.',' ')
	# second, capitalize first letter of each word
	s = capwords(s)
	# third, replace all white space with underscore
	return s.replace(' ','_')  

def ImportMPP(f):
	xml = ''.join(f.chunks())
	root=etree.fromstring(xml)
	
	# read project info
	ns = {'ms' : 'http://schemas.microsoft.com/project'}
	name = root.xpath('/ms:Project/ms:Name', namespaces=ns)[0].text
	
	# at lease one scenario should have been setup by now!
	s = ProjectScenario.objects.all()[0]
	projs = Project.objects.filter(name=name)
	if len(projs) == 0:
		# create project node		
		p = Project()
	else:
		p = projs[0]

	# update project with new information		
	p.name = capwords(name)
	p.start=datetime.today()
	p.end=datetime.today()
	p.scenario = s
	p.save()	
	
	# read and create all Task
	uid_dict=create_task_from_mpp (ns,p,root)
	create_dependency_from_mpp(ns,p,root,uid_dict)
	
def create_task_from_mpp(ns,p,root):
	all_tasks = []
	# iterate the "Task" nodes
	for t in root.xpath('//ms:Task',namespaces=ns):
		# uid
		uid=t.xpath('child::ms:UID',namespaces=ns)[0].text

		try:
			# name
			name=capwords(t.xpath('child::ms:Name',namespaces=ns)[0].text)
		except:
			continue
		
		# level
		level=int(t.xpath('child::ms:OutlineLevel',namespaces=ns)[0].text)	
		
		all_tasks.append((level,name,uid))
		
	temp = {}
	uid_dict={}
	for (level,name,uid) in all_tasks:
		# set up level and parent
		if level-1 in temp: parent=temp[level-1]
		else: temp[level]=parent=None
	
		t=Task.objects.filter(project=p).filter(name=name).filter(level=level).filter(parent=parent)
		if len(t): task = t[0]
		else:
			task = Task()
		# update task contents
		task.project=p
		task.name=name
		task.level=level
		task.parent=parent
		task.save()
		
		# set up uid_dict
		uid_dict[uid]=task
		
		# this can be a parent for others
		temp[level]=task
	return uid_dict
	
def create_dependency_from_mpp(ns,p,root,uid_dict):
	# mpp predecessor type:
	# 1-FS,2-FF,3-SS,4-SF,0-None
	mpp_type={1:('depends','onend'),3:('depends','onstart'),
			2:('precedes','onend'),4:('precedes','onstart')}

	all_to_uid = []
	all_from_uid=[]
	
	for p in root.xpath('//ms:PredecessorLink',namespaces=ns):
		# predecessor UID, this is the to_task
		to_uid = p.xpath('child::ms:PredecessorUID',namespaces=ns)[0].text
		all_to_uid.append(to_uid)
			
		# mpp predecessor type
		p_type=mpp_type[int(p.xpath('child::ms:Type',namespaces=ns)[0].text)]
		
		# parent node is the SELF, this is the from_task
		from_uid = p.xpath('preceding-sibling::ms:UID',namespaces=ns)[0].text
		all_from_uid.append(from_uid)
		
		# look them up
		deps = TaskDependency.objects.filter(from_task=uid_dict[from_uid]).filter(to_task=uid_dict[to_uid])
		if len(deps): dep=deps[0]
		else: dep=TaskDependency()
		
		# update dependencies, wow!
		dep.from_task=uid_dict[from_uid]
		dep.to_task=uid_dict[to_uid]
		dep.type, dep.on_what=p_type
		dep.save()
		
def ImportFreemind(f):
	xml=''.join(f.chunks())
	root = etree.fromstring(xml)
	tree = etree.ElementTree(root)
	
	# one node below root is the project
	proj = root.xpath('/map/node')[0]
	name = capwords(proj.get('TEXT'))
	# at lease one scenario should have been setup by now!
	s = ProjectScenario.objects.all()[0]
	projs = Project.objects.filter(name=name)
	if len(projs) == 0:
		# create project node		
		p = Project()
	else:
		p = projs[0]

	# update project with new information		
	p.name = name
	p.start=datetime.today()
	p.end=datetime.today()
	p.scenario = s
	p.save()
	
	# read and create all the TASKS node, including hiearchy
	create_task_from_freemind(tree,p,root.xpath('//node[@TEXT="Tasks"]')[0])
	create_dependency_from_freemind(tree)
	
def create_dependency_from_freemind(tree):
	# build task dependencies by following arrowlink
	for a in tree.xpath('//arrowlink[@DESTINATION]'):
		from_task = tree.xpath('//node[@ID="%s"]' % a.get('DESTINATION'))[0]
		wbs = from_task.xpath('child::attribute[@NAME="REGISTERED"]')
		if len(wbs):
			id = int(wbs[0].get('VALUE'))
			from_task = Task.objects.get(id=id)
		else: # if no wbs, use full_path
			from_task = Task.objects.get(full_path=tree.getpath(from_task))		
		if from_task is None: 1/0
		
		to_task = a.getparent()
		wbs = to_task.xpath('child::attribute[@NAME="REGISTERED"]')
		if len(wbs):
			id = int(wbs[0].get('VALUE'))
			to_task = Task.objects.get(id=id)
		else: # if no wbs, use full_path
			to_task = Task.objects.get(full_path=tree.getpath(to_task))
		if to_task is None: 1/0		
		
		deps = TaskDependency.objects.filter(from_task=from_task).filter(to_task=to_task)
		if len(deps) == 0:
			dep = TaskDependency()
		else: dep=deps[0]
		dep.from_task=from_task
		dep.to_task=to_task
		dep.type='depends' # hardcoded default, see model.py
		dep.on_what='onend'
		dep.save()
		
def create_task_from_freemind(tree, p, node, parent=None):
	# get task name
	name = capwords(node.get('TEXT'))
			
	# get task wbs if any
	wbs = node.xpath('child::attribute[@NAME="REGISTERED"]')
	if len(wbs):
		id = int(wbs[0].get('VALUE'))
		t = Task.objects.get(id=id)
	else: # if no wbs, use full_path
		full_path=tree.getpath(node)
		tasks = Task.objects.filter(project=p).filter(full_path=full_path).filter(name=name)
	
		if len(tasks) == 0:
			t = Task()
		else:
			t = tasks[0]
		t.full_path = full_path
	# this makes the task to be updated with new information
	t.name = name
	t.project=p
	if parent: t.parent=parent
	t.save()

	# get all the child
	for c in node.xpath('child::node'):
		create_or_update_task(tree,p,c,t)
		

